#pragma once

#include <memory>
#include <fstream>
#include <sstream>
#include <cassert>
#include "util.hpp"

namespace bitlog
{
    using namespace util;
    
    class LogSink
    {
        public:
            using ptr = std::shared_ptr<LogSink>;
            virtual ~LogSink() {}
            virtual void log(const char *data, size_t len) = 0;
    };

    // 落地方向：标准输出
    class StdoutSink : public LogSink
    {
        public:
            // 将日志消息写入到标准输出
            void log(const char *data, size_t len) override
            {
                std::cout.write(data, len);
            }
    };

    // 落地方向：指定文件
    class FileSink : public LogSink
    {
        private:
            std::string _pathname;
            std::ofstream _ofs;
        public:
            // 构造时传入文件名，并打开文件，将操作句柄管理起来
            FileSink(const std::string &pathname)
                : _pathname(pathname)
            {
                // 创建日志文件所存在的目录
                File::createDirector(File::path(_pathname));
                // 创建并打开日志文件
                _ofs.open(_pathname, std::ios::binary | std::ios::app);
                assert(_ofs.is_open());
            }

            void log(const char *data, size_t len) override
            {
                _ofs.write(data, len);
                assert(_ofs.good());
            }

                
    };

    // 落地方向：滚动文件（以大小为限制）
    class RollBySizeSink : public LogSink
    {
        private:
            std::string _basename; // 通过基础文件名 + 扩展文件名（以时间生成）组成一个实际的输出文件名
            std::ofstream _ofs;
            size_t _max_fsize;
            size_t _cur_fsize;
            size_t _namecount;

        public:
            // 构造时传入文件名，并打开文件，将操作句柄管理起来
            RollBySizeSink(const std::string &basename, size_t max_fsize)
                : _basename(basename), _max_fsize(max_fsize), _cur_fsize(0), _namecount(0)
            {
                std::string pathname = createNewFile();
                // 创建日志文件所存在的目录
                File::createDirector(File::path(pathname));
                // 创建并打开日志文件
                _ofs.open(pathname, std::ios::binary | std::ios::app);
                assert(_ofs.is_open());
            }

            void log(const char *data, size_t len) override
            {
                // 写入前进行大小判断。超过指定大小则创建新文件
                if(_cur_fsize >= _max_fsize)
                {
                    _ofs.close();
                    std::string newfile = createNewFile();
                    _ofs.open(newfile, std::ios::binary | std::ios::app);
                    assert(_ofs.is_open());
                    _cur_fsize = 0;
                }
                _ofs.write(data, len);
                assert(_ofs.good());
                _cur_fsize += len;
            }
        
        private:
            std::string createNewFile()
            {
                time_t t = Date::getTime();
                struct tm st;
                localtime_r(&t, &st);
                std::stringstream filename;
                filename << _basename;
                filename << st.tm_year + 1900;
                filename << st.tm_mon + 1;
                filename << st.tm_mday;
                filename << st.tm_hour;
                filename << st.tm_min;
                filename << st.tm_sec;
                filename << "-";
                filename << _namecount++;
                filename << ".log";

                return filename.str();
            }
    };

    // 落地方向：滚动文件（以时间段为限制）
    enum class TimeGap
    {
        GAP_SECOND,
        GAP_MINUTE,
        GAP_HOUR,
        GAP_DAY
    };
    class RollByTimeSink : public LogSink
    {
        private:
            std::string _basename; // 通过基础文件名 + 扩展文件名（以时间生成）组成一个实际的输出文件名
            std::ofstream _ofs;
            size_t _cur; // 当前时间段
            size_t _gap; // 间隔

        public:
            // 构造时传入文件名，并打开文件，将操作句柄管理起来
            RollByTimeSink(const std::string &basename, TimeGap gap)
                : _basename(basename)
            {
                switch(gap)
                {
                    case TimeGap::GAP_SECOND: _gap = 1;break;
                    case TimeGap::GAP_MINUTE: _gap = 60;break;
                    case TimeGap::GAP_HOUR: _gap = 3600;break;
                    case TimeGap::GAP_DAY: _gap = 3600 * 24;break;
                }
                _cur = Date::getTime() / _gap;
                std::string pathname = createNewFile();
                // 创建日志文件所存在的目录
                File::createDirector(File::path(pathname));
                // 创建并打开日志文件
                _ofs.open(pathname, std::ios::binary | std::ios::app);
                assert(_ofs.is_open());
            }

            void log(const char *data, size_t len) override
            {
                // 写入前判断时间段是否相同
                time_t cur_time = Date::getTime();
                if((cur_time / _gap) !=  _cur)
                {
                    _ofs.close();
                    std::string newfile = createNewFile();
                    _ofs.open(newfile, std::ios::binary | std::ios::app);
                    assert(_ofs.is_open());
                    _cur = cur_time / _gap;
                }
                _ofs.write(data, len);
                assert(_ofs.good());
            }
        
        private:
            std::string createNewFile()
            {
                time_t t = Date::getTime();
                struct tm st;
                localtime_r(&t, &st);
                std::stringstream filename;
                filename << _basename;
                filename << st.tm_year + 1900;
                filename << st.tm_mon + 1;
                filename << st.tm_mday;
                filename << st.tm_hour;
                filename << st.tm_min;
                filename << st.tm_sec;
                filename << ".log";

                return filename.str();
            }
    };


    class SinkFactory
    {
        public:
            template<typename SinkType, typename ...Args>
            static LogSink::ptr create(Args &&...args)
            {
                return std::make_shared<SinkType>(std::forward<Args>(args)...);
            }
    };
}